// Package logger provides a simple wrapper around zap logger
package logger

import (
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"time"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"

	"github.com/runningwater/gohub/pkg/app"
)

// Logger 全局日志对象
var Logger *zap.Logger

// Init 初始化日志
func Init(filename string, maxSize, maxBackup, maxAge int, compress bool, logType, level string) {
	// 获取日志写入介质
	writeSyncer := getLogWriter(filename, maxSize, maxBackup, maxAge, compress, logType)
	// 设置日志等级， 具体见 config/log.go
	logLevel := new(zap.AtomicLevel)
	if err := logLevel.UnmarshalText([]byte(level)); err != nil {
		fmt.Println("日志初始化错误，日志级别设置有误。请修改 config/log.go 中的 log.level")
	}

	// 初始化 core
	core := zapcore.NewCore(getEncoder(), writeSyncer, logLevel)
	// 初始化 logger
	Logger = zap.New(core,
		zap.AddCaller(),      // 调用文件和行号，内部使用 runtime.Caller
		zap.AddCallerSkip(1), // 调用文件和行号，内部使用 runtime.Caller
		// zap.Development(), // 开发模式，堆栈跟踪
		zap.AddStacktrace(zapcore.ErrorLevel), // 记录错误级别以上的堆栈信息
	)

	// 设置全局 logger
	// 将自定义的 logger 替换 zap 的全局 logger
	zap.ReplaceGlobals(Logger)
}

// getEncoder 获取编码器(如何写入日志), 输出: 日志格式
func getEncoder() zapcore.Encoder {
	// 日志模式规则
	encoderConfig := zapcore.EncoderConfig{
		TimeKey:        "time",                         // 时间字段名
		LevelKey:       "level",                        // 日志级别字段名
		NameKey:        "logger",                       // 日志名称字段名
		CallerKey:      "caller",                       // 调用者字段名
		FunctionKey:    zapcore.OmitKey,                // 函数名字段名，不显示
		MessageKey:     "msg",                          // 消息字段名
		StacktraceKey:  "stacktrace",                   // 堆栈跟踪字段名
		LineEnding:     zapcore.DefaultLineEnding,      // 每行日志的结尾添加换行符
		EncodeLevel:    zapcore.CapitalLevelEncoder,    // 大写编码器
		EncodeTime:     customTimeEncoder,              // 自定义时间编码器
		EncodeDuration: zapcore.SecondsDurationEncoder, // 以秒为单位显示持续时间
		EncodeCaller:   zapcore.ShortCallerEncoder,     // 短路径编码器
	}

	if app.IsLocal() {
		encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder // 彩色编码器
		// 如果是本地环境，使用 console 编码器
		return zapcore.NewConsoleEncoder(encoderConfig)
	}
	// 如果不是本地环境，使用 JSON 编码器
	return zapcore.NewJSONEncoder(encoderConfig)
}

func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
	// 自定义时间格式
	enc.AppendString(t.Format("2006-01-02 15:04:05"))
}

// getLogWriter 获取日志写入介质
// os.Stdout: 标准输出
// 文件输出
func getLogWriter(filename string, maxSize, maxBackup, maxAge int, compress bool, logType string) zapcore.WriteSyncer {
	// 如果配置了按日期记录日志，则使用按日期记录日志
	if logType == "daily" {
		logName := time.Now().Format("2006-01-02") + ".log"
		filename = strings.Replace(filename, "logs.log", logName, 1)
	}

	// 滚动日志，详见 config/log.go
	lumberJackLogger := &lumberjack.Logger{
		Filename:   filename,  // 日志文件名
		MaxSize:    maxSize,   // 每个日志文件保存的最大尺寸 单位：MB
		MaxBackups: maxBackup, // 日志文件最多保存多少个备份
		MaxAge:     maxAge,    // 文件最多保存多少天
		Compress:   compress,  // 是否压缩
	}
	if app.IsLocal() {
		// 如果是本地环境，使用标准输出和文件输出
		return zapcore.NewMultiWriteSyncer(
			zapcore.AddSync(os.Stdout),
			zapcore.AddSync(lumberJackLogger),
		)
	} else {
		// 生产环境只记录文件
		return zapcore.AddSync(lumberJackLogger)
	}
}

// Dump 调试专用， 不会中断程序，会在终端打印 warning 信息。
// 第一个参数会使用 json.Marshal 进行渲染，第二个参数可选
//
//	logger.Dump(user.User{Name: "test"})
//	logger.Dump(user.User{Name: "test"}, "用户信息")
func Dump(v any, msg ...string) {
	valueStr := jsonString(v)
	if len(msg) > 0 {
		Logger.Warn("Dump", zap.String(msg[0], valueStr))
	} else {
		Logger.Warn("Dump", zap.String("data", valueStr))
	}
}

// LogIf 当 err != nill 时记录 error 等级的日志
func LogIf(err error) {
	if err != nil {
		Logger.Error("Error Occurred: ", zap.Error(err))
	}
}

// LogWarnIf 当 err != nill 时记录 warning 等级的日志
func LogWarnIf(err error) {
	if err != nil {
		Logger.Warn("Error Occurred: ", zap.Error(err))
	}
}

// LogInfoIf 当 err != nill 时记录 info 等级的日志
func LogInfoIf(err error) {
	if err != nil {
		Logger.Info("Error Occurred: ", zap.Error(err))
	}
}

// Debug 调试专用， 不会中断程序
//
//	logger.Debug("Database", zap.String("sql", sql))
func Debug(msg string, fields ...zap.Field) {
	Logger.Debug(msg, fields...)
}

// Info 信息级别
func Info(msg string, fields ...zap.Field) {
	Logger.Info(msg, fields...)
}

// Warn 警告级别
func Warn(msg string, fields ...zap.Field) {
	Logger.Warn(msg, fields...)
}

// Error 错误级别
func Error(msg string, fields ...zap.Field) {
	Logger.Error(msg, fields...)
}

// Fatal 致命级别
func Fatal(msg string, fields ...zap.Field) {
	Logger.Fatal(msg, fields...)
}

// DebugString 记录一条字符串类型的 debug 日志
//
//	logger.DebugString("User", "name", "John")
func DebugString(moduleName, name, msg string) {
	Logger.Debug(moduleName, zap.String(name, msg))
}

func InfoString(moduleName, name, msg string) {
	Logger.Info(moduleName, zap.String(name, msg))
}

func WarnString(moduleName, name, msg string) {
	Logger.Warn(moduleName, zap.String(name, msg))
}

func ErrorString(moduleName, name, msg string) {
	Logger.Error(moduleName, zap.String(name, msg))
}

func FatalString(moduleName, name, msg string) {
	Logger.Fatal(moduleName, zap.String(name, msg))
}

// DebugJSON 记录对象类型的 debug 日志，使用 json.Marshal 进行编码。调用示例：
//
//	logger.DebugJSON("Auth", "读取登录用户", auth.CurrentUser())
func DebugJSON(moduleName, name string, value any) {
	Logger.Debug(moduleName, zap.String(name, jsonString(value)))
}

func InfoJSON(moduleName, name string, value any) {
	Logger.Info(moduleName, zap.String(name, jsonString(value)))
}

func WarnJSON(moduleName, name string, value any) {
	Logger.Warn(moduleName, zap.String(name, jsonString(value)))
}

func ErrorJSON(moduleName, name string, value any) {
	Logger.Error(moduleName, zap.String(name, jsonString(value)))
}

func FatalJSON(moduleName, name string, value any) {
	Logger.Fatal(moduleName, zap.String(name, jsonString(value)))
}

func jsonString(v any) string {
	b, err := json.Marshal(v)
	if err != nil {
		Logger.Error("Logger", zap.String("json.Marshal error", err.Error()))
	}
	return string(b)
}
